package com.example.springsearchengines.service;

import com.example.springsearchengines.config.PathConfig;
import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;


public class DocSearcherService {

    private Index index;

    private Parser parser;

    //停用词路径
//    private final String STOP_INDEX_PATH = "D:/doc_searcher_index/stop_word.txt";

    private static String STOP_INDEX_PATH;

    static {
        if(!PathConfig.isOnline) {
            STOP_INDEX_PATH = "D:/doc_searcher_index/stop_word.txt";
        }else {
            STOP_INDEX_PATH = "/root/spring-search-enigne/stop_word.txt";
        }
    }

    private Set<String> stopWordSet;

    public DocSearcherService() {
        index = new Index();
        parser = new Parser();
        stopWordSet = new HashSet<>();
//        try {
//            parser.runByThread();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        //加载索引到内存中
        index.load();
        //加载停用词数据到set中
        loadStopWord();
    }

    private void loadStopWord() {
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(STOP_INDEX_PATH))) {
            while(true){
                int read = bufferedReader.read();
                if(read < 0) {
                    break;
                }
                stopWordSet.add(String.valueOf((char) read));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //输入为用户给出的查询
    //输出为搜索结果的集合
    public List<Result> search(String query) {
        //1.[分词]针对query这个查询词进行分词
        //已经进行大小写转换，转换为小写
        List<Term> oldTerms = ToAnalysis.parse(query).getTerms();
        List<Term> terms = new ArrayList<>();
        for(Term term : oldTerms) {
            //去掉停用词
            String word = term.getName();
            if(!stopWordSet.contains(word)) {
                terms.add(term);
            }
        }
        //2.[触发]针对分词结果来查倒排
        List<List<Weight>> termsList = new ArrayList<>();
        for(Term term : terms) {
            List<Weight> invertedList = index.getInverted(term.getName());
            if(invertedList == null) {
                //表示倒排索引中没有当前的分词结果
                continue;
            }
            termsList.add(invertedList);
        }
        //3.[合并]针对多个分词结果触发出的相同文档，进行权重合并
        List<Weight> allTermsList = mergeResult(termsList);
        //3.[排序]针对触发的结果按照权重排序降序排序
        //传入比较器
        allTermsList.sort((o1,o2)->{
            return o2.getWeight() - o1.getWeight();
        });
        //4.[包装结果]针对排序的结果，去查正排，构造出要返回的数据
        List<Result> retResults = new ArrayList<>();
        for(Weight weight : allTermsList) {
            DocInfo docInfo = index.getOocInfo(weight.getDocId());
            Result result = new Result();
            result.setTitle(docInfo.getTitle());
            result.setUrl(docInfo.getUrl());
            //设置描述
            result.setDesc(GenDesc(docInfo.getContent(),terms));
            retResults.add(result);
        }
        return retResults;
    }

    class Pos{
        private int col;
        private int row;
        public Pos(int row, int col) {
            this.col = col;
            this.row = row;
        }
    }
    private List<Weight> mergeResult(List<List<Weight>> source) {
        //在进行合并的时候，是把多个行合并成一行了
        //合并的过程中势必是要操作这个二维数组里面的每个元素
        //操作元素就涉及到“行”“列”这样的概念，要想确定二维数组中的一个元素，就需要明确知道行和列

        //1.先针对每一行进行排序（按照id进行升序排序），因为必须是有序的，才可以进行下面的权重合并
        for(List<Weight> weights : source) {
            weights.sort((o1,o2)->{
                return o1.getDocId().compareTo(o2.getDocId());
            });
        }
        //2.借助一个优先级队列，进行合并
        //target表示合并的结果
        List<Weight> target = new ArrayList<>();
        //创建优先级队列，按照docId创建小根堆
        PriorityQueue<Pos> posPriorityQueue = new PriorityQueue<>((o1,o2)->{
            Weight w1 = source.get(o1.row).get(o1.col);
            Weight w2 = source.get(o2.row).get(o2.col);
            return w1.getDocId().compareTo(w2.getDocId());
        });
        //初始化队列，把每一行的第一个元素放到优先级队列中
        for(int i = 0; i < source.size(); i++) {
            posPriorityQueue.offer(new Pos(i,0));
        }
        //循环的取队首元素(也就是相当于这若干行中的最小元素)
        while (!posPriorityQueue.isEmpty()) {
            Pos minPos = posPriorityQueue.poll();
            Weight minWeight = source.get(minPos.row).get(minPos.col);

            //看看这个取到的weight是否和前一个插入到target中的结果是相同的docId
            //如果是就合并
            if(target.size() > 0) {
                Weight lastWeight = target.get(target.size()-1);
                if(lastWeight.getDocId().equals(minWeight.getDocId())) {
                    //此时代表是同一个文档，应该进行合并
                    lastWeight.setWeight(lastWeight.getWeight()+minWeight.getWeight());
                }else {
                    target.add(minWeight);
                }
            }else {
                //当前结果集中没有对应的文档，直接插入即可
                target.add(minWeight);
            }
            //把当前元素处理完成之后，就需要进行把这个元素的对应光标位置往后移，去取当前位置（当前行）的下一个元素
            Pos newPos = new Pos(minPos.row,minPos.col+1);
            if(newPos.col >= source.get(newPos.row).size()) {
                //如果光标移动到超出了这一行的列数的话，就说明到末尾了
                //到达末尾之后说明当前行已经处理完毕
                continue;
            }
            posPriorityQueue.offer(newPos);
        }
        return target;
    }

    private String GenDesc(String content, List<Term> terms) {

        int firstPos = 0;
        //拿分词结果去正文中查看是否存在当前分词
        for(Term term : terms) {
            String word = term.getName();
            //这里的匹配如果只是word的话，进行匹配list的话，会将ArrayList也进行匹配出来，而我们只是想要匹配出list
            //所以加上空格在两边

            //注意此处要进行小写转换，否则匹配会出现问题
            //因为分词的时候就已经进行了小写转换
            //这里只进行了了单个单词的匹配，如果现在有单词list)，如果还是想搜索list就会搜索失败
            //所以可以采用如下方法进行替换：
            //法一：由于indexOf不支持正则匹配的，所以可以采用第三方库进行正则匹配，但是比较复杂
            //法二：使用正则表达式将上述形式替换成旁边带有两个空格的形式
            content = content.replaceAll("\\b" + word +"\\b"," " + word + " ");
            firstPos= content.toLowerCase().indexOf(" " + word + " ");
            if(firstPos > 0) {
                //找到了
                break;
            }
        }
        if(firstPos == -1) {
            //代表当前没有找到
            //此时就直接返回一个空的描述或者也可以取正文的前150字符返回
            if(content.length() <= 150) {
                return content;
            }
            return content.substring(0,150) + "...";
        }
        //找到的第一个字符往前60字符，然后从当前位置往后150个字符为描述
        int begIndex = firstPos < 50 ? 0 : firstPos - 50;
        int endIndex = begIndex + 150 < content.length() ? (begIndex + 150) : content.length();
        String desc = content.substring(begIndex,endIndex) + "...";
        for (Term term : terms) {
            String word = term.getName();
            //当查询词为list的时候，不能把arraylist标红，所以加上空格
            //(?i)代表不区分大小写进行匹配
            desc = desc.replaceAll("(?i) " + word + " ","<i>" + word + "<i>");
        }
        return desc;
    }

//    public static void main(String[] args) throws InterruptedException {
//        DocSearcherService docSearcher = new DocSearcherService();
//        List<Result> results = docSearcher.search("ArrayList");
//        System.out.println("length: " +results.size());
//        for(Result result : results) {
//            System.out.println("-----------------");
//            System.out.println(result);
//        }
//    }
}
